using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design.Serialization;
using System.Globalization;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Permissions;
using Microsoft.WowAddonStudio.FrameXml.Serialization;
using Microsoft.WowAddonStudio.FrameXml.Services;

namespace Microsoft.WowAddonStudio.FrameXml.Design.Serialization
{
	/// <summary>
	/// Provides the base class for storing serialization data for the System.ComponentModel.Design.Serialization.ComponentSerializationService.
	/// </summary>
	[Serializable]
	public sealed class FrameXmlSerializationStore : SerializationStore, ISerializable
	{
		private readonly IServiceProvider serviceProvider;
		private readonly Dictionary<string, string> elements = new Dictionary<string, string>();
		private readonly Dictionary<string, Type> runtimeTypes = new Dictionary<string, Type>();
		private readonly List<string> names = new List<string>();

		/// <summary>
		/// Initializes a new instance of the <see cref="FrameXmlSerializationStore"/> class.
		/// </summary>
		/// <param name="serviceProvider">The service provider.</param>
		public FrameXmlSerializationStore(IServiceProvider serviceProvider)
		{
			if (serviceProvider == null)
				throw new ArgumentNullException("serviceProvider");

			this.serviceProvider = serviceProvider;
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="FrameXmlSerializationStore"/> class.
		/// </summary>
		/// <param name="info">The info.</param>
		/// <param name="context">The context.</param>
		public FrameXmlSerializationStore(SerializationInfo info, StreamingContext context)
		{
			elements = (Dictionary<string, string>) info.GetValue("Elements", typeof (Dictionary<string, string>));
			runtimeTypes = (Dictionary<string, Type>) info.GetValue("RuntimeTypes", typeof (Dictionary<string, Type>));
			names = (List<string>) info.GetValue("Names", typeof (List<string>));
		}


		/// <summary>
		/// Gets the count of serialized elements.
		/// </summary>
		/// <value>The count.</value>
		public int Count
		{
			get { return elements.Count; }
		}

		/// <summary>
		/// Deserializes the specified store.
		/// </summary>
		/// <param name="provider">The service provider.</param>
		/// <param name="serializer">The serializer.</param>
		/// <returns></returns>
		public ICollection Deserialize(IServiceProvider provider, IFrameXmlSerializer serializer)
		{
			return Deserialize(provider, null, serializer);
		}

		/// <summary>
		/// Deserializes the specified store.
		/// </summary>
		/// <param name="provider">The service provider.</param>
		/// <param name="container">The container.</param>
		/// <param name="serializer">The serializer.</param>
		/// <returns></returns>
		public ICollection Deserialize(IServiceProvider provider, IContainer container, IFrameXmlSerializer serializer)
		{
			if (provider == null)
				throw new ArgumentNullException("provider");

			if (serializer == null)
				throw new ArgumentNullException("serializer");


			var objects = new List<InheritableObject>();

			foreach (KeyValuePair<string, string> element in elements)
			{
				object deserializedObject = serializer.Deserialize(element.Value, runtimeTypes[element.Key]);
				if (deserializedObject != null)
				{
					objects.Add(deserializedObject as InheritableObject);
				}
			}

			return objects;
		}

		/// <summary>
		/// Adds the object.
		/// </summary>
		/// <param name="serializer">The serializer.</param>
		/// <param name="serializationObject">The value.</param>
		internal void AddObject(IFrameXmlSerializer serializer, object serializationObject)
		{
			if (serializationObject != null)
			{
				var name = GetObjectName(serializationObject as INamedObject);
				if (!elements.ContainsKey(name))
				{
					string serializedObject = serializer.Serialize(serializationObject);
					names.Add(name);
					runtimeTypes.Add(name, serializationObject.GetType());
					elements.Add(name, serializedObject);
				}
			}
		}

		/// <summary>
		/// Loads the specified stream.
		/// </summary>
		/// <param name="stream">The stream.</param>
		/// <returns></returns>
		internal static FrameXmlSerializationStore Load(Stream stream)
		{
			var formatter = new BinaryFormatter();
			return (FrameXmlSerializationStore) formatter.Deserialize(stream);
		}

		/// <summary>
		/// Gets the name of the object.
		/// </summary>
		/// <param name="value">The value.</param>
		/// <returns></returns>
		private static string GetObjectName(INamedObject value)
		{
			if (value != null && !string.IsNullOrEmpty(value.Name))
				return value.Name;

			string str = Guid.NewGuid().ToString().Replace("-", "_");
			return string.Format(CultureInfo.CurrentCulture, "object_{0}", new object[] {str});
		}

		/// <summary>
		/// Gets the name of the object.
		/// </summary>
		/// <param name="value">The value.</param>
		/// <returns></returns>
		private static string GetObjectName(object value)
		{
			var component = value as IComponent;
			if (component != null)
			{
				var site = component.Site;
				if (site != null)
				{
					var site2 = site as INestedSite;
					if ((site2 != null) && !string.IsNullOrEmpty(site2.FullName))
					{
						return site2.FullName;
					}
					if (!string.IsNullOrEmpty(site.Name))
					{
						return site.Name;
					}
				}
			}
			string str = Guid.NewGuid().ToString().Replace("-", "_");
			return string.Format(CultureInfo.CurrentCulture, "object_{0}", new object[] {str});
		}


		/// <summary>
		/// Closes the serialization store.
		/// </summary>
		public override void Close()
		{
			//Do nothing.
		}

		/// <summary>
		/// Gets a collection of errors that occurred during serialization or deserialization.
		/// </summary>
		/// <value></value>
		/// <returns>An <see cref="T:System.Collections.ICollection"/> that contains errors that occurred during serialization or deserialization.</returns>
		public override ICollection Errors
		{
			get { return new object[0]; }
		}

		/// <summary>
		/// Gets the service provider.
		/// </summary>
		/// <value>The service provider.</value>
		public IServiceProvider ServiceProvider
		{
			get { return serviceProvider; }
		}

		/// <summary>
		/// Saves the store to the given stream.
		/// </summary>
		/// <param name="stream">The stream to which the store will be serialized.</param>
		public override void Save(Stream stream)
		{
			Close();
			new BinaryFormatter().Serialize(stream, this);
		}

		#region ISerializable Members

		/// <summary>
		/// Populates a <see cref="T:System.Runtime.Serialization.SerializationInfo"/> with the data needed to serialize the target object.
		/// </summary>
		/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> to populate with data.</param>
		/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext"/>) for this serialization.</param>
		/// <exception cref="T:System.Security.SecurityException">The caller does not have the required permission. </exception>
		[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
		public void GetObjectData(SerializationInfo info, StreamingContext context)
		{
			info.AddValue("Elements", elements);
			info.AddValue("Names", names);
			info.AddValue("RuntimeTypes", runtimeTypes);
		}

		#endregion
	}
}